

local require = require
local ngx     = ngx
local debug   = debug
local string  = string
local type    = type
local table   = table
local pairs   = pairs
local tonumber = tonumber
local table_loadstring = table.loadstring

----数据变量----
local uu         = require("modules.commhm.utils")
local log        = require("modules.commhm.log")
local ns_time    = require("modules.commhm.time")
local ns_network = require("modules.commhm.network")

local ns_config  = require("modules.dc_gate.dc_gate_config");

local ns_auth_checker = require("modules.dc_gate.auth_checker");

local ns_data_cache_ip_allow  = require( "modules.data_cache.data_cache_ip_allow" )

local string_sub = string.sub
local table_tostring = table.tostring
local table_insert = table.insert

----------------------------------------------------
log.debug( "init gg----" );
--ngx.cache = ngx.cache or {};   --全局变量区

--上下文变量
--ngx.ctx = {
	--m_params        = {},
	--m_profile_data  = {},
	--m_ma_task_data  = {},
	--m_user_data_is_dirty = false,
--}


local FOO = {
	_VERSION = '0.12.1',
	__INT32__  = 2147483648;	
	__UINT32__ = 4294967296;
	data_cache = {};

	carry_index = 0,  ---post http carry

	ver_lua = 'dc_gate_20210927f',   --代码版本
	ver_hot = 0,                     --热更版本
};


FOO.KEYS = 
{
};


FOO.init = function()
end


-- 正常退出
FOO.final = function()
	if  ngx.ctx.gg_final_has_called then
		-- 防止无限循环 只调一次
	else
		ngx.ctx.gg_final_has_called = true;
	end
end


-- 初始化白名单
function FOO.init_ip_allow()
	for dc_name_, dc_config_ in pairs( ns_config.servers ) do
		if  type(dc_config_) == 'table' then
			for  k, v in pairs( dc_config_ ) do
				if  type(v) == 'table' then
					if  v.ip_allow then
						ns_data_cache_ip_allow.add_ip_allow( k, v.ip_allow )   ---IP白名单
					else
						log.error( "WARNING:not_set_dc_ip_allow:" .. k, "ip_allow" )
					end
				end
			end
		end
	end
end



---检查ip白名单
function FOO.check_ip_allow_by_name()
	return ns_data_cache_ip_allow.check_ip_allow( ngx.ctx.m_params.name, ngx.var.remote_addr )
end


---网关已经验证白名单后，产生一个token，给后续的dc使用
function FOO.gen_dc_gate_token()
	local token_ = ns_data_cache_ip_allow.gen_dc_gate_token()
	return  '&from_dc_gate=' .. token_
end



-- 异常退出
FOO.error_exit = function( txt )
	FOO.final();
	log.error(txt);
	ns_time.print_cost_all();
	ngx.exit(0);
end


--普通退出
FOO.normal_exit = function( txt )
	FOO.final();
	log.debug(txt);
	ns_time.print_cost_all();
	ngx.exit(0);	
end


--按格式返回 lua table or json
FOO.return_table = function( t )
	t = t or {};

	if  type(t) ~= 'table' then
		t = { ret=t };
	end

	if  ngx.ctx.m_params.json == '1' then
		--json ajax 跨域
		ngx.header["Access-Control-Allow-Origin"] = "*";
		ngx.header["Server"] = "php";

		--log.debug("return json");
		local jret = uu.table2json( t );
		ngx.say(jret);

	elseif ngx.ctx.m_params.log == '1' then
		--log
		log.debug( "<br><br>=========================return begin========" );
		uu.var_dump(t)
		log.debug( "=========================return end========<br><br>" );

	else
		--lua
		local ret = table_tostring( t );
		ngx.say( ret);
	end
end


--- ["/miniw/achieve"]     = { ip='211.159.183.137', port=8079 },
--- 按照ip和port，计算出 url = http://211.159.183.137:8079
function FOO.get_url_by_path( path_ )
	if  type(path_) == 'table' then
		if  not path_.url then
			if  path_.ip and path_.port then
				path_.url = "http://" .. path_.ip .. ":" .. path_.port
			else
				path_.url = ""
			end
		end
		return path_.url
	end
	return ""
end




---获得mm_read的path （ path配置里有select才会触发 ）
function FOO.get_mm_read_url_by_path( config_path_, tail_ )
	local config_path_select_ = config_path_.select[ tail_ ]
	if  config_path_select_ then
		return 'http://' .. config_path_select_.ip .. ':' .. config_path_select_.port
	else
		return 'http://' .. config_path_.ip .. ':' .. config_path_.port
	end
end



--组合拆分mread请求
--批量请求(顺序发送)
--批量请求( thread并发模式，速度稍快，但是性能略低 )
-- name = map acc_new
-- wid_list = { {key=1011, hash=2, tail=11}, {key=1022, hash=4, tail=22} }
-- config_path 用来计算url路径
function   FOO.multi_mm_read_data_thread( name_, wid_list, config_path_ )
	--按尾数调用
	local ret_fin_      = {};
	local all_wid_list_ = {};

	for i=1,  #wid_list do
		ret_fin_[i] = { kh = wid_list[i].key .. '_h' .. wid_list[i].hash }
		--wid_list[i].__req_mread_id = i;

		if  wid_list[i].__do_nothing then
			--只是占位 用来返回空结果
		else
			local tail_ = wid_list[i].tail

			--local server_id_ = ns_config.ssdb_db_select[ tail_ ];
			--同尾数合用一个db的情况
			--all_wid_list_[server_id_] = all_wid_list_[server_id_] or { __tail=tail_ };
			--table.insert( all_wid_list_[tail_], wid_list[i] );

			all_wid_list_[tail_] = all_wid_list_[tail_] or { __tail=tail_, kv_list_temp = {} };
			table_insert( all_wid_list_[tail_].kv_list_temp, wid_list[i].key )
			table_insert( all_wid_list_[tail_].kv_list_temp, wid_list[i].hash )

		end
	end

	--uu.var_dump( all_wid_list_ );
	local ret_stdmap_ = {};
	local thread_list_ = {};

	--分离请求 设置到map中
	for k, v in pairs( all_wid_list_ ) do
		local function thread_wrap_()
			local get_pp_ = {
				name    = name_,
				cmd     = 'comm',
				func    = 'mread',
				auth    = 'Xcn67K',
				select  = v.__tail,
				uin     = ngx.ctx.m_params.uin,
				carry   = ngx.ctx.m_params.carry,
			}

			local post_pp_ = {
				kv_list = table.concat( v.kv_list_temp, '-' ),
			}

			local url_       = FOO.get_mm_read_url_by_path( config_path_, v.__tail ) .. ngx.var.document_uri
			local ret_string = FOO.private_post_mread_to_dc_server( url_, get_pp_, post_pp_, 3000 )
			log.debug( "ret_string=" .. uu.to_str(ret_string) )
			if  ret_string then
				local ret_ = table_loadstring( ret_string )
				if  ret_ then
					for m, n in pairs( ret_ ) do
						if  n.key and n.hash then
							ret_stdmap_[ n.key .. '_h' .. n.hash ] = n
						end
					end
					uu.var_dump( ret_stdmap_ )
				else
					log.error( "error_ret_not_table" )
				end
			else
				log.error( "error_ret_not_string" )
			end
		end
		uu.thread_spawn( thread_wrap_, thread_list_ )
	end

	uu.thread_wait( thread_list_ )

	uu.var_dump( ret_stdmap_ )

	--合并结果
	for i=1, #ret_fin_ do
		local kh_ = ret_fin_[i].kh
		if  ret_stdmap_[ kh_ ] then
			ret_fin_[i] = ret_stdmap_[ kh_ ] or {};
		end
	end

	return ret_fin_;
end



---发送请求到服务器 POST ( gate --> 单个dc )
--- p_url_root_   发送的url地址
--- pp_get_       get参数
--- pp_post_      post参数
--- p_time_exp_   超时时间 默认9秒
function FOO.private_post_mread_to_dc_server( p_url_root_, pp_get_, pp_post_, p_time_exp_ )

	-- set carry
	if  not pp_get_.carry then
		FOO.carry_index = FOO.carry_index + 1
		pp_get_.carry   = FOO.carry_index
	end
	local Carry_ = '' .. pp_get_.carry

	-- create url and send
	local url_ = p_url_root_ .. '?' .. ngx.encode_args(pp_get_) .. FOO.gen_dc_gate_token()
	local post_data_ = ngx.encode_args(pp_post_);
	local ret_, body_, resp_ = ns_network.postHttpPage( url_, post_data_, p_time_exp_ )

	if  log.fast() then
		log.debug( url_ )
		log.debug( post_data_ )
		log.debug( "mysql post ret=" .. ret_ .. ", body=" .. (body_ or 'nil') )
	end

	if  ret_ == 200 then
		--验证Carry
		if  resp_.headers.Carry == Carry_ then
			if  resp_.headers["Content-Length"] then
				if  resp_.headers["Content-Length"] ==  ('' .. #body_) then
					return body_   --ok
				else
					log.day_list_simple( "dc_mysql", "dc_client", "DataLen_check_error",
							resp_.headers["Content-Length"], #body_ )
				end
			else
				ngx.ctx.__no_Content_Length__ = 1
				return body_       --ok 没有Content-Length
			end
		else
			log.day_list_simple( "dc_mysql", "dc_client", "Carry_check_error", resp_.headers.Carry, Carry_ )
		end
	else
		log.day_list_simple( "dc_mysql", "dc_client", "http_error", ret_, url_ )
	end

	if  log.fast() then
		log.debug( post_data_ );
		log.debug( "url=" .. url_ )
	end

	FOO.error_exit( 'error select = ' .. (pp_get_.select or 'nil') );
end


-- 拆分设置dc配置文件
function FOO.decode_dc_client_config( config_ )
	local ret_path_config_
	if  config_ and config_.server and config_.select then
		--[[
		server = { [0] = { url = 'http://120.24.64.132:8186/dc/?auth=Xcn67K&name=map' }, },
		select = { [0]=0, [1]=0, [2]=0, [3]=0, [4]=0, [5]=0, [6]=0, [7]=0, [8]=0, [9]=0, }
		--]]
		ret_path_config_ = {}

		for select_, id_ in pairs(config_.select) do
			if  config_.server[id_] then
				if  config_.server[id_].url then
					local t_ = uu.split( config_.server[id_].url, '/' )
					if  type(t_) == 'table' and #t_ > 3 then
						local mm_ = uu.split( t_[3], ':' )
						if  type(mm_) == 'table' and #mm_ == 2 then
							local server1_ = { ip=mm_[1], port=tonumber(mm_[2]) }
							if  not (ret_path_config_.ip and ret_path_config_.port) then
								--首次赋值
								ret_path_config_.ip   = server1_.ip
								ret_path_config_.port = server1_.port
							elseif ret_path_config_.ip == server1_.ip and ret_path_config_.port == server1_.port then
								--select可以合并
							else
								--无法合并
								if  not ret_path_config_.select then
									ret_path_config_.select = {}
								end
								ret_path_config_.select[ select_ ] = server1_
							end
						end
					end
				end
			end
		end
	end

	return ret_path_config_
end


return FOO;
